--- 模块功能：数据链路激活、SOCKET管理(创建、连接、数据收发、状态维护)
-- @module socket
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2020.03.03
require "link"
require "utils"
module(..., package.seeall)
local sockets = {}
-- 单次发送数据最大值
local SENDSIZE = 11200
--- SOCKET 是否有可用
-- @return 可用true,不可用false
socket.isReady = link.isReady
-- 创建socket函数
local mt = {}
mt.__index = mt
--- 创建socket对象
-- @string host: 服务器地址,支持域名和IP地址
-- @number port: 服务器端口,支持数字或数字字符串
-- @string prot,可选值"tcp","udp","ssl"
-- @table[opt=nil] cert，ssl连接需要的证书配置，只有protocol参数为"tcp"时，才参数才有意义，cert格式如下：
--  {
--     caCert = "ca.crt", --CA证书文件(Base64编码 X.509格式)，如果存在此参数，则表示客户端会对服务器的证书进行校验；不存在则不校验
--     clientCert = "client.crt", --客户端证书文件(Base64编码 X.509格式)，服务器对客户端的证书进行校验时会用到此参数
--     clientKey = "client.key", --客户端私钥文件(Base64编码 X.509格式)
--     clientPassword = "123456", --客户端证书文件密码[可选]
--  }
-- @return client，创建成功返回socket客户端对象；创建失败返回nil
-- @usage
-- c=socket.new("www.163.com",80,"udp")
-- c=socket.new("www.163.com",80,"tcp")
-- c=socket.new("www.163.com",80,"ssl")
-- c=socket.new("www.163.com",80,"tcp",{caCert="ca.crt"})
-- c=socket.new("www.163.com",80,"tcp",{caCert="ca.crt",clientCert="client.crt",clientKey="client.key"})
-- c=socket.new("www.163.com",80,"tcp",{caCert="ca.crt",clientCert="client.crt",clientKey="client.key",clientPassword="123456"})
function new(host, port, prot, cert)
    local tcpssl = prot and prot:upper():find("SSL")
    prot = prot and prot:upper():sub(1, 3) or "TCP"
    local function readCert(f)
        if not f then return end
        f = f:sub(1, 1) == "/" and f or "/lua/" .. f
        if io.exists(f) then return io.readFile(f) end
    end
    return setmetatable({
        id = nil,
        host = host,
        port = port,
        timeout = nil,
        retrymin = nil,
        retrymax = nil,
        prot = prot,
        ssl = tcpssl,
        cert = {
            caCert = cert and readCert(cert.caCert),
            clientCert = cert and readCert(cert.clientCert),
            clientKey = cert and readCert(cert.clientKey),
            clientPassword = cert and cert.clientPassword or "",
        },
        co = nil, -- 主线程
        sco = nil, -- send 线程
        input = "",
        output = {},
        putbuff = {},
        callbacks = {},
        wait = "idle",
        sendwait = "idle",
        dns = nil,
        dnsidx = 1,
        unread = 0, -- TCP滑窗时未读取的数据长度
        connected = false,
        overflow = false,
        maxValue = 32768, -- 缓冲区最大值
        flen = {["*l"] = 1, ["*L"] = 2, ["*s"] = 1, ["*n"] = 1, ["*p"] = 1},
        ft = {["*l"] = "\n", ["*L"] = "\r\n", ["*s"] = "%s", ["*n"] = "%d", ["*p"] = "%p"}
    }, mt)
end
--- 创建基于TCP的socket对象(旧接口)
-- @bool[opt=nil] ssl，是否为ssl连接，true表示是，其余表示否
-- @table[opt=nil] cert，ssl连接需要的证书配置，只有ssl参数为true时，才参数才有意义，cert格式如下：
-- {
--     caCert = "ca.crt", --CA证书文件(Base64编码 X.509格式)，如果存在此参数，则表示客户端会对服务器的证书进行校验；不存在则不校验
--     clientCert = "client.crt", --客户端证书文件(Base64编码 X.509格式)，服务器对客户端的证书进行校验时会用到此参数
--     clientKey = "client.key", --客户端私钥文件(Base64编码 X.509格式)
--     clientPassword = "123456", --客户端证书文件密码[可选]
-- }
-- @return client，创建成功返回socket客户端对象；创建失败返回nil
-- @usage
-- c = socket.tcp()
-- c = socket.tcp(true)
-- c = socket.tcp(true, {caCert="ca.crt"})
-- c = socket.tcp(true, {caCert="ca.crt", clientCert="client.crt", clientKey="client.key"})
-- c = socket.tcp(true, {caCert="ca.crt", clientCert="client.crt", clientKey="client.key", clientPassword="123456"})
function tcp(ssl, cert)
    return new(nil, nil, ssl == true and "SSL" or "TCP", cert)
end
--- 创建基于UDP的socket对象(旧接口)
-- @return client，创建成功返回socket客户端对象；创建失败返回nil
-- @usage c = socket.udp()
function udp()
    return new(nil, nil, "UDP")
end
--- socket:on 注册函数
-- @string event,事件，可选值"recv","sent","open","close","error"
-- @function callback,回调方法,形参见例子
-- @usage mt:on("recv",function(msg)print(msg)end)
function mt:on(event, callback)
    self.callbacks[event] = callback
end
--- 获取当前缓冲区的数据长度
-- @return number
-- @usage c:available()
function mt:available()
    return #self.input
end
--- 获取socket的当前状态
-- @return string："idle","recv","sent","connect","close","error"
-- @usage c:status()
function mt:status()
    return self.wait
end
--- 获取socket与服务器连接是否在在线
-- @return boolean: true 连接成功,false,连接失败
-- @usage c:online()
function mt:online()
    return self.connected
end
--- 获取通道接收缓冲区大小
-- @return number
-- @usage c:getRecvBuf()
function mt:getRecvBuf()
    return self.maxValue
end
--- 设置通道接收缓冲区大小
-- @number v：不小于8192的值
-- @usage c:setRecvBuf()
function mt:setRecvBuf(v)
    v = tonumber(v) or self.maxValue
    self.maxValue = v > self.maxValue and v or self.maxValue
end
--- 获取一次能发送数据的最大长度
-- @return number
-- @usage c:getSendSize()
function mt:getSendSize()
    return SENDSIZE
end
--- 注册截止符
-- @string word : recv(timeout,format, frameTimeout)的format 可选参数
-- @string val : format 对应的正则表达式
-- @string len : 正则表达式匹配关键字的最大长度
-- @return nil
-- @usage mt:regFormat("*S"," ",1)
-- @usage mt:regFormat("*xyz","xyz",3)
-- @usage mt:regFormat("*t","[%s%p]",1)
function mt:regFormat(cutWord, val, len)
    if not cutWord or not val then return end
    len = tonumber(len) and tonumber(len) or 1
    self.ft[cutWord], self.flen[cutWord] = val, len
end
--- 连接服务器
-- @string[opt=nil]host: new()方法时可为nil,服务器地址支持ip和域名
-- @number[opt=nil]port: new()方法时可为nil,服务器端口
-- @number [opt=nil]timeout, 链接服务器最长超时时间
-- @param [opt=nil]userdns,用户自定义DNS服务器,为string时为单DNS服务器
-- @param [opt=nil]userdns,用户自定义DNS服务器,为table时为多个DNS服务器
-- @param [opt=nil]userdns,用户自定义DNS服务器,为true时为使用DNS库的服务器
-- @return bool result true - 成功，false - 失败
-- @usage c = socket.new(); c:connect()
-- @usage c = socket.tcp(); c:connect("www.baidu.com",80,120)
-- @usage c = socket.tcp(); c:connect("www.baidu.com",80,120,true)
-- @usage c = socket.tcp(); c:connect("www.baidu.com",80,120,"114.114.114.114")
-- @usage c = socket.tcp(); c:connect("www.baidu.com",80,120,{"114.114.114.114","114.114.115.115"})
function mt:connect(host, port, timeout, userdns)
    self.co, self.timeout = coroutine.running(), timeout
    self.host, self.port = host or self.host, port or self.port
    if not link.isReady() then
        log.warn("socket.connect: ip not ready!")
        return false
    end
    if userdns and self.wait == "DNS_ERR" then
        local function setDNS(primary, secondary)
            log.info("socket DNS:", primary, secondary)
            ril.request('AT*NETDNS=5,"' .. primary .. '","' .. (secondary or "") .. '"')
            ril.request("AT*NETDNS=5")
        end
        if type(userdns) == "table" then
            self.dns = userdns
        elseif type(userdns) == "string" then
            self.dns = {userdns}
        else
            self.dns = {
                "114.114.114.114", -- 114 DNS
                "114.114.115.115",
                "223.5.5.5", --阿里 DNS
                "223.6.6.6",
                "119.29.29.29", -- 腾讯 DNS
                "182.254.116.116",
                "1.2.4.8", -- 中国互联网中心 DNS
                "180.76.76.76", -- 百度 DNS
                "208.67.220.220", -- Open DNS
                "8.8.8.8", -- 谷歌 DNS
            }
        end
        setDNS(self.dns[self.dnsidx], self.dns[self.dnsidx + 1])
        self.dnsidx = 2 + self.dnsidx
        if self.dnsidx > #self.dns then
            self.dnsidx = 1
            log.error("socket:connect:", "无法解析域名！")
        end
    end
    self.wait = "connecting"
    self.id = socketcore.sock_conn(self.ssl and 2 or (self.prot == "TCP" and 0) or 1, self.host, self.port, self.cert)
    if not self.id then
        log.error("socket:connect: Domain name cannot be resolved:", self.id, self.host, self.port)
        return false
    end
    log.info("socket connect:coreid,prot,host,port,timeout:", self.id, self.prot, self.host, self.port, timeout or 120)
    sockets[self.id] = self
    if self.co and not sys.wait((timeout or 120) * 1000) then
        socketcore.sock_close(self.id)
        -- socketcore.sock_destroy(self.id)
        return coroutine.yield()
    end
    return true
end
--- 读取socket缓冲区的数据(非阻塞)
-- @number [opt=nil]length，number类型，不填则读取整个缓冲区
-- @return string,用户读取的数据长度,无数据返回""
-- @usage mt:read(1460)
function mt:read(length)
    local len = tonumber(length)
    local str = len and self.input:sub(1, len) or self.input
    self.input = len and self.input:sub(length + 1, -1) or ""
    if self.overflow then self.input = self.input .. socketcore.sock_recv(self.id, self.unread) end
    return str
end
--- 接收数据(阻塞接口,必须任务线程中使用)
-- @number[opt=0] timeout 可选参数，接收超时时间，单位毫秒
-- @string[opt=nil] msg 可选参数，控制socket所在的线程退出recv阻塞状态
-- @bool[opt=nil] msgNoResume 可选参数，控制socket所在的线程退出recv阻塞状态，false或者nil表示“在recv阻塞状态，收到msg消息，可以退出阻塞状态”，true表示不退出
-- @return result 数据接收结果，true表示成功，false表示失败
-- @return data 如果成功的话，返回接收到的数据；超时时返回错误为"timeout"；msg控制退出时返回msg的字符串
-- @return param 如果是msg返回的false，则data的值是msg，param的值是msg的参数
-- @usage c = socket.tcp(); c:connect()
-- @usage result, data = c:recv()
-- @usage false,msg,param = c:recv(60000,"publish_msg")
--- 读取socket数据,支持字节流和帧模式,线程中使用
-- @number[opt=nil] timeout: 等待缓冲区非空的超时,nil为一直阻塞
-- @string[opt=nil] message：退出recv阻塞的消息,nil 为不支持消息退出
-- 帧模式支持定界，定长，定时进行数据分帧
-- 定界默认支持自定义截止符,默认截止符如下：
-- @param[opt=nil] format: 帧模式的截止符
--      *l：读取到结束字符\n或超时
--      *s：读取到空格字符或者超时
--      数字，读取指定长度的数据或超时
--      nil, 超时后读取缓冲区的全部数据
-- @number[opt=nil]  frameTimeout: 与 format 搭配的超时设置
-- format和frameTimeout 组合模式如下：
--      format=nil, waitTime=nil : 为默认的字节流模式
--      format=nil, frameTimeout=number,为帧模式的定时模式(超时返回所有数据)
--      format="*l", frameTimeout=nil,定界模式,读取到\n返回数据,否则阻塞
--      format=number, frameTimeout=nil,定长模式,读取指定长度数据,否则阻塞
--      format="*l", frameTimeout=number,定界模式,读取到\n返回数据,否则超时后返回false
--      format=number, frameTimeout=number,定长模式,读取指定长度数据,否则超时后返回false
-- @return result,data,param: 有三个返回值，组合如下：
-- @return true,data,nil ：返回接收到的数据；
-- @return false,"timeout",nil : 缓冲区非空超时
-- @return false message,param ：收到message消息,param为消息携带的数据
-- @return false,nil,nil : socket 发生错误
function mt:recv(timeout, message, format, frameTimeout)
    local line, idx, state, option = nil, 1, "recv", false
    self.co, self.wait = coroutine.running(), "recv"
    if message and not self.msg then
        self.msg = message
        self.subtopic = function(str)
            table.insert(self.output, str)
            if self.wait == "recv" then
                coroutine.resume(self.co, "self.msg")
            end
        end
        sys.subscribe(self.msg, self.subtopic)
    end
    -- 处理历史消息中断的数据
    if #self.output > 0 then
        return false, self.msg, table.remove(self.output, 1)
    end
    repeat
        if type(format) == "number" then
            line = #self.input >= format and self.input:sub(1, format)
        elseif self.ft[format] then
            local _, endidx = self.input:find(".-" .. self.ft[format], idx)
            if endidx then
                line = self.input:sub(1, endidx)
            else
                idx = #self.input > self.flen[format] and #self.input - self.flen[format] or 1
            end
        elseif type(frameTimeout) == "number" then
            option = true
        else
            line = #self.input > 0 and self.input
        end
        if line then
            break
        else
            state = sys.wait(#self.input > 0 and frameTimeout or timeout)
            if option and state == nil then line = self.input end
        end
    until state ~= "recv"
    self.wait = "idle"
    if line then
        self.input = self.input:sub(#line + 1, -1)
        if self.overflow then self.input = self.input .. socketcore.sock_recv(self.id, self.unread) end
        if self.callbacks.recv then self.callbacks.recv(line) end
        return true, line
    elseif state == "self.msg" then
        return false, self.msg, table.remove(self.output, 1)
    elseif state == nil then
        return false, "timeout"
    else
        return false, "offline"
    end
end
--- 发送数据(非阻塞) 最大11200字节
-- @string str: 要发送给服务器的字符串
-- @return boole:true 为成功,其他为失败
function mt:write(str)
    if not self.connected then
        log.warn("socket:write, connected:", self.connected)
        return false
    end
    if str then
        local res = xpcall(function()
            self.putbuff = table.merge(self.putbuff, str:split(SENDSIZE))
        end, function()print(debug.traceback()) end)
        if not res then
            log.warn("socket:write, wrong data type:", type(str))
            return false
        end
    end
    if self.sendwait == "idle" and #self.putbuff > 0 then
        self.sendwait, str = "sent", table.remove(self.putbuff, 1)
        socketcore.sock_send(self.id, str)
    end
    return true
end
--- 发送数据
-- @string str: 要发送给服务器的字符串
-- @number[opt=30] timeout 可选参数，发送超时时间，单位秒
-- @return result true - 成功，false - 失败
-- @usage  c = socket.tcp(); c:connect(); c:send("12345678");
function mt:send(str, timeout)
    local res = self:write(str)
    self.sco = coroutine.running()
    if res and self.sco then
        return sys.wait((tonumber(timeout) or 30) * 1000) or false
    end
    return res
end
--- 异步收发选择器
-- @number keepAlive,服务器和客户端最大通信间隔时间,也叫心跳包最大时间,单位秒
-- @string pingreq,心跳包的字符串
-- @return boole,false 失败，true 表示成功
function mt:asyncSelect(keepAlive, pingreq)
    assert(self.co == coroutine.running(), "socket:asyncSelect: coroutine mismatch")
    if keepAlive and keepAlive ~= 0 then
        if type(pingreq) == "function" then
            sys.timerStart(pingreq, keepAlive * 1000)
        else
            sys.timerStart(self.asyncSend, keepAlive * 1000, self, pingreq or "\0")
        end
    end
    return coroutine.yield()
end
--- 异步发送数据
-- @string data 数据
-- @number[opt=120] timeout 可选参数，发送超时时间，单位秒
-- @return result true - 成功，false - 失败
-- @usage  c = socket.tcp(); c:connect(); c:asyncSend("12345678");
function mt:asyncSend(data, timeout)
    return self:send(data, timeout)
end
--- 异步接收数据
-- @number len: 要读取的数据长度,默认读取所有缓冲区的数据
-- @return data: 返回指定长度的字符串
-- @usage c = socket.tcp(); c:connect()
-- @usage data = c:asyncRecv()
function mt:asyncRecv(len)
    return self:read(len)
end
--- 销毁当前socket
-- @retrun nil
-- @usage mt:destroy()
function mt:destroy()
    if self.subtopic then
        sys.unsubscribe(self.msg, self.subtopic)
        self.msg = nil
    end
    self.sendwait = "idle"
    self.connected, self.wait = false, "closed"
    sockets[self.id], self.id = nil, nil
end
--- 断开当前连接
-- @return nil
-- @usage  c = socket.tcp(); c:connect(); c:send("123"); c:close()
function mt:close()
    self.wait = "closing"
    if self.id then
        if self.connected then
            socketcore.sock_close(self.id)
            if self.co and self.co == coroutine.running() then coroutine.yield() end
        end
    end
    self:destroy()
end
rtos.on(rtos.MSG_SOCK_CLOSE_CNF, function(msg)
    if not sockets[msg.socket_index] then
        log.warn("socket.onClosing:", 'response on nil socket', msg.socket_index)
        return
    end
    log.info("socket.onClosing: id,wait,result", msg.socket_index, sockets[msg.socket_index].wait, msg.result == 0)
    socketcore.sock_destroy(msg.socket_index)
    if sockets[msg.socket_index].callbacks.close then
        sockets[msg.socket_index].callbacks.close()
    end
    if sockets[msg.socket_index].co then
        coroutine.resume(sockets[msg.socket_index].co, false)
    end
end)
rtos.on(rtos.MSG_SOCK_CONN_CNF, function(msg)
    if not sockets[msg.socket_index] then
        log.warn("socket.onConnect:", 'response on nil socket', msg.socket_index)
        return
    end
    log.info("socket.onConnect: id,wait,result", msg.socket_index, sockets[msg.socket_index].wait, msg.result == 0)
    sockets[msg.socket_index].connected = msg.result == 0
    sockets[msg.socket_index].wait = msg.result == 0 and "connected" or "closing"
    if msg.result == 0 then
        if sockets[msg.socket_index].callbacks.open then
            sockets[msg.socket_index].callbacks.open()
        end
        sys.publish("SOCKET_ACTIVE", msg.result == 0)
    else
        -- socketcore.sock_close(msg.socket_index)
        end
    if sockets[msg.socket_index].co then
        coroutine.resume(sockets[msg.socket_index].co, msg.result == 0)
    end
end)
rtos.on(rtos.MSG_SOCK_SEND_CNF, function(msg)
    if not sockets[msg.socket_index] then
        log.warn("socket.onSent:", 'response on nil socket', msg.socket_index)
        return
    end
    log.info("socket.onSent: id,sendwait,result", msg.socket_index, sockets[msg.socket_index].sendwait, msg.result == 0)
    sockets[msg.socket_index].sendwait = "idle"
    if #sockets[msg.socket_index].putbuff > 0 and msg.result == 0 then
        sockets[msg.socket_index].write(sockets[msg.socket_index])
        log.info("socket.onSent:", "Next on the send queue!")
    else
        if sockets[msg.socket_index].callbacks.sent then
            sockets[msg.socket_index].callbacks.sent(msg.result == 0)
        end
        if sockets[msg.socket_index].sco then
            coroutine.resume(sockets[msg.socket_index].sco, msg.result == 0)
        end
    end
end)
rtos.on(rtos.MSG_SOCK_CLOSE_IND, function(msg)
    if not sockets[msg.socket_index] then
        log.warn("socket.onClosed:", 'close ind on nil socket', msg.socket_index)
        return
    end
    log.info("socket.offline: id,wait,result", msg.socket_index, sockets[msg.socket_index].wait, msg.result == 0)
    sys.publish("SOCKET_ACTIVE", false)
    if sockets[msg.socket_index].callbacks.error then
        sockets[msg.socket_index].callbacks.error(sockets[msg.socket_index], "connection break")
    end
    if sockets[msg.socket_index].co then
        coroutine.resume(sockets[msg.socket_index].co, false)
    end
end)
rtos.on(rtos.MSG_SOCK_RECV_IND, function(msg)
    if not sockets[msg.socket_index] then
        log.warn("socket.onReceive:", 'receive ind on nil socket', msg.socket_index)
        return
    end
    log.info("socket.onReceive: id,wait,result", msg.socket_index, sockets[msg.socket_index].wait, msg.result == 0)
    if #sockets[msg.socket_index].input < sockets[msg.socket_index].maxValue then
        sockets[msg.socket_index].input = sockets[msg.socket_index].input .. socketcore.sock_recv(msg.socket_index, msg.recv_len)
        sockets[msg.socket_index].overflow = false
    else
        sockets[msg.socket_index].unread = msg.recv_len
        sockets[msg.socket_index].overflow = true
    end
    sys.publish("SOCKET_RECV", msg.socket_index, sockets[msg.socket_index])-- 兼容之前代码
    if sockets[msg.socket_index].co and sockets[msg.socket_index].wait == "recv" then
        coroutine.resume(sockets[msg.socket_index].co, "recv")
    end
end)

--- 设置TCP层自动重传的参数
-- @number[opt=4] retryCnt，重传次数；取值范围0到12
-- @number[opt=16] retryMaxTimeout，限制每次重传允许的最大超时时间(单位秒)，取值范围1到16
-- @return nil
-- @usage
-- setTcpResendPara(3,8)
-- setTcpResendPara(4,16)
function setTcpResendPara(retryCnt, retryMaxTimeout)
    ril.request("AT+TCPUSERPARAM=6," .. (retryCnt or 4) .. ",7200," .. (retryMaxTimeout or 16))
end

--- 设置域名解析参数
-- 注意：0027以及之后的core版本才支持此功能
-- @number[opt=4] retryCnt，重传次数；取值范围1到8
-- @number[opt=4] retryTimeoutMulti，重传超时时间倍数，取值范围1到5
--                第n次重传超时时间的计算方式为：第n次的重传超时基数*retryTimeoutMulti，单位为秒
--                重传超时基数表为{1, 1, 2, 4, 4, 4, 4, 4}
--                第1次重传超时时间为：1*retryTimeoutMulti 秒
--                第2次重传超时时间为：1*retryTimeoutMulti 秒
--                第3次重传超时时间为：2*retryTimeoutMulti 秒
--                ...........................................
--                第8次重传超时时间为：8*retryTimeoutMulti 秒
-- @return nil
-- @usage
-- socket.setDnsParsePara(8,5)
function setDnsParsePara(retryCnt, retryTimeoutMulti)
    ril.request("AT*DNSTMOUT=" .. (retryCnt or 4) .. "," .. (retryTimeoutMulti or 4))
end

-- 移动场景关闭时候的处理：
sys.subscribe("IP_ERROR_IND", function()
    for k, v in pairs(sockets) do
        if v.callbacks.error then v.callbacks.error(v, false) end
        if v.co and coroutine.status(v.co) == "suspended" then
            coroutine.resume(v.co, false)
        end
    end
end)
--- 需要在任务中启动,带自动重连,支持心跳协议
-- @number[opt=nil] keepAlive：网络连接保活时间(心跳包),单位秒
-- @string[opt=nil] message: 同recv()的 message 参数
-- @param[opt=nil] format: 同recv()的 format 参数
-- @parma[opt=nil] frameTimeout: 同recv()的 frameTimeout 参数
-- @string[opt=nil] heart：心跳包字符串
-- @function[opt=nil] proc 处理服务器下发消息的函数
-- @return 无
-- @usage sys.taskInit(mt.start,mt,180,"heart",nil,25)
-- @usage sys.taskInit(mt.start,mt,180,"heart",nil,25,nil,function(msg)u1:send(msg) end)
function mt:start(keepAlive, message, format, frameTimeout, heart, proc)
    keepAlive = tonumber(keepAlive) and keepAlive * 1000 or nil
    while true do
        while not socket.isReady() do sys.wait(1000) end
        while not self:connect() do sys.wait(2000) end
        while true do
            local r, s, p = self:recv(keepAlive, message, format, frameTimeout)
            if r then
                if proc then proc(s) end
            elseif message and s == message then
                log.info("send message:", p)
                if not self:send(p) then break end
            elseif s == "timeout" and heart then
                if not self:send(tostring(heart)) then break end
            else
                log.error('socket receive error', r, s)
                break
            end
        end
        self:close()
        sys.wait(2000)
    end
end
